#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "common.h"
#include "ctrl_channel.h"
#include "data_channel.h"
#include "logger.h"
#include "file.h"

#ifdef _WIN32
#include <crtdbg.h>
#endif

enum {
	CLIENT_ERROR = -1,
	CLIENT_SUCCESS = 0
};

typedef struct command_t {
	int command;
	char* remote_path;
	FILE* local_file;
} command_t;

logger_t log;

static int run_client(char* host, int port, int mode, int command, char* remote_path, FILE* local_file);
static int connect_to_server(char* host, int port, link_t* link);
static void send_command(link_t* ctrl, int mode, void* arg, int* abort);
static int file_transfer(link_t * ctrl, link_t* data, int cmd, char * filename, FILE* file);

int main(int argc, char** argv)
{
	char* host, *port_str;
	FILE* file = NULL;
	int port, mode, cmd;

	if (argc != 6) {
		DPRINT("[c] usage: %s host:port cmd mode filename logfile", argv[0]);
		goto out;
	}

	if (logger_init(&log, argv[5]) != LOGGER_SUCCESS) {
		DPRINT("[c] couldn't open log file: %s", argv[5]);
		goto out;
	}

	/* get host and port */
	host = argv[1];
	port_str = strstr(host, ":");
	if (port_str == NULL) {
		DPRINT("[c] invalid host:port format");
		goto clean;
	}

	*(port_str++) = '\0';
	port = atoi(port_str);

	/* get cmd */
	if (strcmp(argv[2], "s") == 0) {
		cmd = STOR;
	}
	else if (strcmp(argv[2], "r") == 0) {
		cmd = RETR;
	}
	else {
		DPRINT("[c] invalid cmd: %s - use 's' or 'r'", argv[2]);
		goto clean;
	}

	/* get mode */
	if (strcmp(argv[3], "a") == 0) {
		mode = ACTIVE;
	}
	else if (strcmp(argv[3], "p") == 0) {
		mode = PASSIVE;
	}
	else {
		DPRINT("[c] invalid mode: %s - use 'a' or 'p'", argv[3]);
		goto clean;
	}

	/* open file */
	file = fopen(argv[4], cmd == STOR ? "rb" : "wb");
	if (file == NULL) {
		DPRINT("[c] failed to open file %s", argv[4]);
		goto clean;
	}

	link_init();

	if (run_client(host, port, mode, cmd, argv[4], file) != CLIENT_ERROR) {
		DPRINT("[c] command executed successfully");
	}

clean:
	logger_destroy(&log);
	if (file != NULL) {
		fclose(file);
	}

out:

	check_leaks();

	return 0;
}

static int run_client(char* host, int port, int mode, int command, char* remote_path, FILE* local_file)
{
	link_t ctrl, data;
	command_t cmd;
	int ret = CLIENT_ERROR;

	if (connect_to_server(host, port, &ctrl) != CLIENT_SUCCESS) {
		ret = CLIENT_ERROR;
		goto out;
	}

	DPRINT("[c] connection to server established");

	cmd.command = command;
	cmd.remote_path = remote_path;
	cmd.local_file = local_file;
	if (data_setup_master(&ctrl, &data, mode, send_command, &cmd) != DATA_SUCCESS) {
		ret = CLIENT_ERROR;
		goto out;
	}

	DPRINT("[c] data link established");

	if (file_transfer(&ctrl, &data, command, remote_path, local_file) == CLIENT_ERROR) {
		DPRINT("[c] command was NOT executed");
		ret = CLIENT_ERROR;
		goto out;
	}

	DPRINT("[c] command executed");

	ret = CLIENT_SUCCESS;

out:
	link_close(&ctrl); /* data is closed by file_transfer */
	return ret;
}

static int connect_to_server(char* host, int port, link_t* link)
{
	struct hostent* hent;

	hent = gethostbyname(host);
	if (hent == NULL) {
		DPRINT("[c] no such host: %s", host);
		return CLIENT_ERROR;
	}

	link->addr = *((int *) hent->h_addr);
	link->port = port;

	if (link_connect(link) != LINK_SUCCESS) {
		return CLIENT_ERROR;
	}

	return CLIENT_SUCCESS;
}

static void send_command(link_t* ctrl, int mode, void* arg, int* abort)
{
	command_t* cmd = (command_t *) arg;
	ctrl_message msg;

	msg.ctrl = cmd->command;
	strcpy(msg.filename, cmd->remote_path);

	if (mode == ACTIVE) {
		ctrl_message reply;
		if (ctrl_send_receive(ctrl, &msg, &reply) != CTRL_SUCCESS) {
			DPRINT("[c] failed to send RETR/STOR");
			goto err;
		}

		if (reply.ctrl != OPEN_DATA) {
			DPRINT("[c] the server could not execute the command");
			goto err;
		}
	}
	else if (mode == PASSIVE) {
		if (ctrl_send_receive(ctrl, &msg, NULL) != CTRL_SUCCESS) {
			DPRINT("[c] failed to send RETR/STOR");
			goto err;
		}
	}
	else {
		assert(0);
	}

	return;

err:
	*abort = 1;
}

static int file_transfer(link_t * ctrl, link_t* data, int cmd, char * filename, FILE* file)
{
	ctrl_message reply;

	switch (cmd) {
	case RETR:
		DPRINT("[c] recving file '%s' ...", filename);

		if (FILE_SUCCESS != file_recv(data, file)) {
			DPRINT("[c] FAILED (file_recv failed)");
			goto cmd_err;
		}

		break;

	case STOR:	
		DPRINT("[c] sending file '%s' ...", filename);

		if (FILE_SUCCESS != file_send(data, file)) {
			DPRINT("[c] FAILED (file_send failed)");
			goto cmd_err;
		}

		break;

	default:
		assert(0);
		return CLIENT_ERROR;
	}

	DPRINT("[c] waiting for DATA_FIN");

	if ((ctrl_send_receive(ctrl, NULL, &reply) != CTRL_SUCCESS) || (reply.ctrl != DATA_FIN)) {
		DPRINT("[c] FAILED (couldn't recv DATA_FIN)");
		return CTRL_ERROR;
	}

	return CLIENT_SUCCESS;

cmd_err:
	DPRINT("[c] closing data connecting");
	link_close(data);

	/* read the error */
	DPRINT("[c] waiting for error");

	if ((ctrl_send_receive(ctrl, NULL, &reply) != CTRL_SUCCESS)) {
		DPRINT("[c] couldn't receive error from server");
	}
	else {
		/* we shouldn't get DATA_FIN on error */
		assert(reply.ctrl != DATA_FIN);
	}

	return CLIENT_ERROR;	
}
